#include "Chessboard.h"
#include "../Game/Move.h"
//#include <cmath>

namespace Core
{
	Chessboard::Chessboard()
	{
		for (int i = 0; i < RANKS; i++)
			for (int j = 0; j < COLUMNS; j++)
				_squares[i][j] = new Square(this, i, j);
	}

	Chessboard::~Chessboard(void)
	{
		for (int i = 0; i < RANKS; i++)
			for (int j = 0; j < COLUMNS; j++)
				delete _squares[i][j];
	}

	bool Chessboard::checkBounds(int r1, int r2, int r3, int r4, bool throwEx = true)
	{
		int r[4] = { r1, r2, r3, r4 };
		for (int i = 0; i < 4; i++)
		{
			if(r[i] >= RANKS || r[i] < 0)
			{
				if(throwEx)
					throw "Chessboard: Argument out of range";
				return false;
			}
		}

		return true;
	}

	int Chessboard::sign(int val)
	{
		return val / abs(val);
	}

	Square * Chessboard::at(int r, int c) const
	{
		if(r < 0 || r >= RANKS || c < 0 || c >= COLUMNS)
			throw "Bounds of square was out of range";
		return _squares[r][c];
	}

	Piece * Chessboard::findPieceDirect(int r1, int c1, int r2, int c2, bool contnu, map<int, Move*>* validMoves) const
	{
		checkBounds(r1, r2, c1, c2);

		if(r1 != r2 && c1 != c2)
			throw "Chessboard: Path is not direct";
		
		Piece * p = at(r1, c1)->piece();
		if(r1 == r2)
		{
			int d = sign(c2 - c1);
			int c3 = contnu ? (c1 < c2 ? COLUMNS : -1) : c2 - c1 + d;

			for (int i = c1 + d; i != c3; i += d)
			{
				if(validMoves != NULL)
				{
					Move * mv = new Move(p, at(r1, i));
					if(p == NULL || p->checkDestination(mv))
						(*validMoves)[mv->to()->key()] = mv;
				}
				if(at(r1, i)->piece() != NULL)
					return at(r1, i)->piece();
			}
		}

		else if(c1 == c2)
		{
			int d = sign(r2 - r1);
			int r3 = contnu ? (r1 < r2 ? RANKS : -1) : r1 - r2 + d;

			for (int i = r1 + d; i != r3; i += d)
			{
				if(validMoves != NULL)
				{
					Move * mv = new Move(p, at(i, c1));
					if(p == NULL || p->checkDestination(mv))
						(*validMoves)[mv->to()->key()] = mv;
				}
				if(at(i, c1)->piece() != NULL)
					return at(i, c1)->piece();
			}
		}

		return NULL;
	}

	Piece * Chessboard::findPieceDiagonal(int r1, int c1, int r2, int c2, bool contnu, map<int, Move*>* validMoves) const
	{
		checkBounds(r1, r2, c1, c2);
		if(abs(r1 - r2) != abs(c1 - c2))
			throw "Chessboard: Path is not diagonal";
		Piece * p = at(r1, c1)->piece();
		int dr = sign(r2 - r1);
		int dc = sign(c2 - c1);
		while(1)
		{
			r1+=dr;
			c1+=dc;
			if(contnu && !checkBounds(r1, c1, 0, 0, false))
					break;
			else if(!contnu && (r1 == r2+dr || c1 == c2+dc))
				break;
			
			if(validMoves != NULL)
			{
				Move * mv = new Move(p, at(r1, c1));
				if(p == NULL || p->checkDestination(mv))
					(*validMoves)[mv->to()->key()] = mv;
			}
				
			if(at(r1, c1)->piece() != NULL)
				return at(r1, c1)->piece();
		}

		return NULL;
	}

	bool Chessboard::underAttackBy(int r, int c, ChColor own, vector<Piece*> * attackers, bool firstPiece) const
	{
		checkBounds(r, c, 0, 0);

		Piece * p;
		int size = attackers->size();

		// traversing all 8 surrounding squares
		for (int i = -1; i <= +1; i++)
			for (int j = -1; j <= +1; j++)
			{
				if(i == 0 && j == 0)
					continue;
				if(firstPiece && attackers->size() > size)
					break;

				p = findPieceIfExist(r, c, r+i, c+j);
				if(i != 0 && j != 0)
				{
					if(isAttackingDiagonal(p, r, c, own))	
						attackers->push_back(p);
				}
				else if(isAttackingDirect(p, r, c, own))
					attackers->push_back(p);
			}

		// traversing the 8 possible positions of
		int a[] = { -1, -2, +1, +2 };
		for (int k = 0; k <= 4; k++)
			for (int l = 0; l <= 4; l++)
			{
				if(abs(a[k]) == abs(a[l]))
					continue;
				if(firstPiece && attackers->size() > size)
					break;
				try
				{
					p = at(r+a[k], c+a[l])->piece(); // might throw exception (out of range)
					if(p != NULL && p->color() != own && p->type() == ChKnight)
						return p;
				}
				catch(...) { /* bypass */ }
			}

		return attackers->size() > size;
	}

	Piece * Chessboard::underAttackBy(int r, int c, ChColor own) const
	{
		vector<Piece *> v;
		bool found = underAttackBy(r, c, own, &v, true);
		return found ? v[0] : NULL;
	}

	Piece * Chessboard::findPieceIfExist(int r1, int c1, int r2, int c2) const
	{
		Piece * p = NULL;
		try
		{
			if(r1 == r2 || c1 == c2)
				p = findPieceDirect(r1, c1, r2, c2);
			else
				p = findPieceDiagonal(r1, c1, r2, c2);
		}
		catch(...) { }

		return p;
	}

	bool Chessboard::isAttackingDirect(Piece * p, int r, int c, ChColor own) const
	{
		if(p == NULL || p->color() == own)
			return false;

		switch(p->type())
		{
		case ChKing:
			return abs(p->square()->row() - r) == 1;
		case ChQueen:
		case ChRook:
			return true;
		}

		return false;
	}

	bool Chessboard::isAttackingDiagonal(Piece * p, int r, int c, ChColor own) const
	{
		if(p == NULL || p->color() == own)
			return false;

		switch(p->type())
		{
		case ChKing:
		case ChPawn:
			return abs(p->square()->row() - r) == 1;
		case ChQueen:
		case ChBishop:
			return true;
		}

		return false;
	}

}
